import os
import threading
import time
import tkinter as tk
from stat import S_ISDIR
from tkinter import scrolledtext, messagebox

import paramiko
import ttkbootstrap as ttk
from scp import SCPClient
from ttkbootstrap.constants import *

from app.util.annotation_util import annotate, ClassInfo
from app.view.base_view import View


@annotate(ClassInfo(name="SSH", category="dev_tool"))
class SshView(View):
    """设置视图"""

    def __init__(self, parent, controller):
        super().__init__(parent, controller)
        # 初始化变量
        # SSH连接相关变量
        self.ssh_client = None
        self.sftp_client = None
        self.is_connected = False

        # 创建主框架
        self.main_frame = ttk.Frame(self, padding=10)
        self.main_frame.pack(fill=BOTH, expand=True)

        # 创建连接面板
        self.create_connection_panel()

        # 创建分隔符
        ttk.Separator(self.main_frame, orient=HORIZONTAL).pack(fill=X, pady=5)

        # 创建主内容区域（使用Notebook）
        self.notebook = ttk.Notebook(self.main_frame)
        self.notebook.pack(fill=BOTH, expand=True, pady=5)

        # 创建终端标签页
        self.terminal_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.terminal_frame, text="终端")
        self.create_terminal_panel()

        # 创建文件传输标签页
        self.file_transfer_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.file_transfer_frame, text="文件传输")
        self.create_file_transfer_panel()

        # 绑定关闭事件
        # self.protocol("WM_DELETE_WINDOW", self.on_close)

    def create_connection_panel(self):
        """创建连接设置面板"""
        conn_frame = ttk.LabelFrame(self.main_frame, text="连接设置", padding=10)
        conn_frame.pack(fill=X, pady=5)

        # 主机设置
        ttk.Label(conn_frame, text="主机:").grid(row=0, column=0, padx=5, pady=5, sticky=W)
        self.host_var = ttk.StringVar(value="localhost")
        ttk.Entry(conn_frame, textvariable=self.host_var, width=20).grid(row=0, column=1, padx=5, pady=5, sticky=W)

        # 端口设置
        ttk.Label(conn_frame, text="端口:").grid(row=0, column=2, padx=5, pady=5, sticky=W)
        self.port_var = ttk.StringVar(value="22")
        ttk.Entry(conn_frame, textvariable=self.port_var, width=10).grid(row=0, column=3, padx=5, pady=5, sticky=W)

        # 用户名设置
        ttk.Label(conn_frame, text="用户名:").grid(row=0, column=4, padx=5, pady=5, sticky=W)
        self.username_var = ttk.StringVar(value="root")
        ttk.Entry(conn_frame, textvariable=self.username_var, width=20).grid(row=0, column=5, padx=5, pady=5, sticky=W)

        # 密码设置
        ttk.Label(conn_frame, text="密码:").grid(row=0, column=6, padx=5, pady=5, sticky=W)
        self.password_var = ttk.StringVar()
        ttk.Entry(conn_frame, textvariable=self.password_var, show="*", width=20).grid(row=0, column=7, padx=5, pady=5,
                                                                                       sticky=W)

        # 连接按钮
        self.connect_btn = ttk.Button(conn_frame, text="连接", command=self.toggle_connection)
        self.connect_btn.grid(row=0, column=8, padx=5, pady=5)

        # 连接状态
        self.connection_status_var = ttk.StringVar(value="未连接")
        self.status_label = ttk.Label(conn_frame, textvariable=self.connection_status_var, foreground="red")
        self.status_label.grid(row=0, column=9, padx=5, pady=5, sticky=W)

    def create_terminal_panel(self):
        """创建终端面板"""
        # 输出区域
        output_frame = ttk.LabelFrame(self.terminal_frame, text="输出", padding=5)
        output_frame.pack(fill=BOTH, expand=True, pady=5)

        self.output_text = scrolledtext.ScrolledText(output_frame, wrap=tk.WORD, width=80, height=20)
        self.output_text.pack(fill=BOTH, expand=True)
        self.output_text.config(state=DISABLED)

        # 命令输入区域
        cmd_frame = ttk.Frame(self.terminal_frame)
        cmd_frame.pack(fill=X, pady=5)

        ttk.Label(cmd_frame, text="命令:").pack(side=LEFT, padx=5)
        self.cmd_var = ttk.StringVar()
        cmd_entry = ttk.Entry(cmd_frame, textvariable=self.cmd_var, width=80)
        cmd_entry.pack(side=LEFT, padx=5, fill=X, expand=True)
        cmd_entry.bind("<Return>", lambda event: self.execute_command())

        ttk.Button(cmd_frame, text="执行", command=self.execute_command).pack(side=LEFT, padx=5)

    def create_file_transfer_panel(self):
        """创建文件传输面板"""
        # 创建左右分栏
        paned_window = ttk.PanedWindow(self.file_transfer_frame, orient=HORIZONTAL)
        paned_window.pack(fill=BOTH, expand=True, pady=5)

        # 本地文件系统面板
        local_frame = ttk.LabelFrame(paned_window, text="本地文件系统", padding=5)
        paned_window.add(local_frame, weight=1)

        # 本地路径显示
        local_path_frame = ttk.Frame(local_frame)
        local_path_frame.pack(fill=X, pady=5)

        self.local_path_var = ttk.StringVar(value=os.getcwd())
        ttk.Entry(local_path_frame, textvariable=self.local_path_var).pack(side=LEFT, fill=X, expand=True, padx=5)
        ttk.Button(local_path_frame, text="刷新", command=self.refresh_local_files).pack(side=LEFT, padx=5)

        # 本地文件列表
        self.local_files_tree = ttk.Treeview(local_frame)
        self.local_files_tree.pack(fill=BOTH, expand=True, pady=5)
        self.local_files_tree["columns"] = ("size", "modified")
        self.local_files_tree.column("#0", width=300, minwidth=200, stretch=YES)
        self.local_files_tree.column("size", width=100, minwidth=50, stretch=NO)
        self.local_files_tree.column("modified", width=150, minwidth=100, stretch=NO)
        self.local_files_tree.heading("#0", text="名称", anchor=W)
        self.local_files_tree.heading("size", text="大小", anchor=W)
        self.local_files_tree.heading("modified", text="修改时间", anchor=W)

        # 绑定双击事件
        self.local_files_tree.bind("<Double-1>", self.navigate_local_path)

        # 刷新本地文件
        self.refresh_local_files()

        # 传输按钮面板
        transfer_buttons_frame = ttk.Frame(paned_window, width=50)
        paned_window.add(transfer_buttons_frame, weight=0)

        ttk.Button(transfer_buttons_frame, text="上传 ->", command=self.upload_files).pack(pady=10, padx=5)
        ttk.Button(transfer_buttons_frame, text="<- 下载", command=self.download_files).pack(pady=10, padx=5)

        # 远程文件系统面板
        remote_frame = ttk.LabelFrame(paned_window, text="远程文件系统", padding=5)
        paned_window.add(remote_frame, weight=1)

        # 远程路径显示
        remote_path_frame = ttk.Frame(remote_frame)
        remote_path_frame.pack(fill=X, pady=5)

        self.remote_path_var = ttk.StringVar(value="/")
        ttk.Entry(remote_path_frame, textvariable=self.remote_path_var).pack(side=LEFT, fill=X, expand=True, padx=5)
        ttk.Button(remote_path_frame, text="刷新", command=self.refresh_remote_files).pack(side=LEFT, padx=5)

        # 远程文件列表
        self.remote_files_tree = ttk.Treeview(remote_frame)
        self.remote_files_tree.pack(fill=BOTH, expand=True, pady=5)
        self.remote_files_tree["columns"] = ("size", "modified")
        self.remote_files_tree.column("#0", width=300, minwidth=200, stretch=YES)
        self.remote_files_tree.column("size", width=100, minwidth=50, stretch=NO)
        self.remote_files_tree.column("modified", width=150, minwidth=100, stretch=NO)
        self.remote_files_tree.heading("#0", text="名称", anchor=W)
        self.remote_files_tree.heading("size", text="大小", anchor=W)
        self.remote_files_tree.heading("modified", text="修改时间", anchor=W)

        # 绑定双击事件
        self.remote_files_tree.bind("<Double-1>", self.navigate_remote_path)

    def toggle_connection(self):
        """切换连接状态（连接/断开）"""
        if not self.is_connected:
            self.connect()
        else:
            self.disconnect()

    def connect(self):
        """连接到SSH服务器"""
        host = self.host_var.get()
        port = int(self.port_var.get())
        username = self.username_var.get()
        password = self.password_var.get()

        # 在新线程中执行连接操作，避免UI冻结
        threading.Thread(target=self._connect_thread, args=(host, port, username, password), daemon=True).start()

    def _connect_thread(self, host, port, username, password):
        """连接线程"""
        try:
            # 更新UI状态
            self.after(0, lambda: self._update_connection_status("连接中...", "orange"))
            self.after(0, lambda: self.connect_btn.config(state=DISABLED))

            # 创建SSH客户端
            self.ssh_client = paramiko.SSHClient()
            self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接服务器
            self.ssh_client.connect(host, port, username, password, timeout=10)

            # 创建SFTP客户端
            self.sftp_client = self.ssh_client.open_sftp()

            # 更新连接状态
            self.is_connected = True
            self.after(0, lambda: self._update_connection_status(f"已连接到 {host}:{port}", "green"))
            self.after(0, lambda: self.connect_btn.config(text="断开"))
            self.after(0, lambda: self.refresh_remote_files())

            # 在终端显示连接信息
            self.after(0, lambda: self.append_output(f"成功连接到 {host}:{port}\n"))

        except Exception as e:
            # 连接失败处理
            self.is_connected = False
            self.ssh_client = None
            self.sftp_client = None
            self.after(0, lambda: self._update_connection_status(f"连接失败: {str(e)}", "red"))
            self.after(0, lambda: messagebox.showerror("连接错误", f"无法连接到服务器: {str(e)}"))
        finally:
            # 恢复按钮状态
            self.after(0, lambda: self.connect_btn.config(state=NORMAL))

    def _update_connection_status(self, text, color):
        """更新连接状态显示"""
        self.connection_status_var.set(text)
        self.status_label.config(foreground=color)

    def disconnect(self):
        """断开SSH连接"""
        if self.ssh_client:
            try:
                self.ssh_client.close()
                self.sftp_client = None
            except Exception as e:
                messagebox.showerror("断开错误", f"断开连接时出错: {str(e)}")

        self.is_connected = False
        self.ssh_client = None
        self._update_connection_status("未连接", "red")
        self.connect_btn.config(text="连接")
        self.append_output("已断开连接\n")

    def execute_command(self):
        """执行命令"""
        if not self.is_connected:
            messagebox.showwarning("未连接", "请先连接到服务器")
            return

        cmd = self.cmd_var.get().strip()
        if not cmd:
            return

        # 在终端显示命令
        self.append_output(f"[{self.username_var.get()}@{self.host_var.get()}] $ {cmd}\n")

        # 清空命令输入
        self.cmd_var.set("")

        # 在新线程中执行命令
        threading.Thread(target=self._execute_command_thread, args=(cmd,), daemon=True).start()

    def _execute_command_thread(self, cmd):
        """执行命令的线程"""
        try:
            # 执行命令
            stdin, stdout, stderr = self.ssh_client.exec_command(cmd)

            # 读取输出
            output = stdout.read().decode()
            error = stderr.read().decode()

            # 显示输出
            if output:
                self.after(0, lambda: self.append_output(output))
            if error:
                self.after(0, lambda: self.append_output(f"错误: {error}"))

            self.after(0, lambda: self.append_output("\n"))

        except Exception as e:
            self.after(0, lambda: self.append_output(f"执行命令时出错: {str(e)}\n"))

    def append_output(self, text):
        """向输出区域添加文本"""
        self.output_text.config(state=NORMAL)
        self.output_text.insert(END, text)
        self.output_text.see(END)  # 滚动到底部
        self.output_text.config(state=DISABLED)

    def refresh_local_files(self):
        """刷新本地文件列表"""
        path = self.local_path_var.get()

        # 清空现有内容
        for item in self.local_files_tree.get_children():
            self.local_files_tree.delete(item)

        try:
            # 获取父目录
            parent_dir = os.path.dirname(path)
            if parent_dir and parent_dir != path:
                self.local_files_tree.insert("", END, text="..", values=("目录", ""))

            # 获取文件列表
            with os.scandir(path) as entries:
                for entry in entries:
                    try:
                        if entry.is_dir(follow_symlinks=False):
                            self.local_files_tree.insert("", END, text=entry.name,
                                                         values=("目录", time.ctime(entry.stat().st_mtime)))
                        else:
                            size = self.format_size(entry.stat().st_size)
                            self.local_files_tree.insert("", END, text=entry.name,
                                                         values=(size, time.ctime(entry.stat().st_mtime)))
                    except Exception as e:
                        print(f"无法读取文件 {entry.name}: {e}")

        except Exception as e:
            messagebox.showerror("错误", f"无法读取目录: {str(e)}")

    def navigate_local_path(self, event):
        """导航到本地目录"""
        selected_item = self.local_files_tree.selection()
        if not selected_item:
            return

        item_text = self.local_files_tree.item(selected_item[0], "text")
        current_path = self.local_path_var.get()

        if item_text == "..":
            new_path = os.path.dirname(current_path)
        else:
            new_path = os.path.join(current_path, item_text)

        if os.path.isdir(new_path):
            self.local_path_var.set(new_path)
            self.refresh_local_files()

    def refresh_remote_files(self):
        """刷新远程文件列表"""
        if not self.is_connected or not self.sftp_client:
            return

        path = self.remote_path_var.get()

        # 清空现有内容
        for item in self.remote_files_tree.get_children():
            self.remote_files_tree.delete(item)

        try:
            # 获取父目录
            if path != "/":
                parent_dir = os.path.dirname(path)
                if not parent_dir:
                    parent_dir = "/"
                self.remote_files_tree.insert("", END, text="..", values=("目录", ""))

            # 获取文件列表
            entries = self.sftp_client.listdir_attr(path)
            for entry in entries:
                if S_ISDIR(entry.st_mode):
                    self.remote_files_tree.insert("", END, text=entry.filename,
                                                  values=("目录", self.format_time(entry.st_mtime)))
                else:
                    size = self.format_size(entry.st_size)
                    self.remote_files_tree.insert("", END, text=entry.filename,
                                                  values=(size, self.format_time(entry.st_mtime)))

        except Exception as e:
            messagebox.showerror("错误", f"无法读取远程目录: {str(e)}")

    def navigate_remote_path(self, event):
        """导航到远程目录"""
        if not self.is_connected or not self.sftp_client:
            return

        selected_item = self.remote_files_tree.selection()
        if not selected_item:
            return

        item_text = self.remote_files_tree.item(selected_item[0], "text")
        current_path = self.remote_path_var.get()

        if item_text == "..":
            new_path = os.path.dirname(current_path)
            if not new_path:
                new_path = "/"
        else:
            if current_path == "/":
                new_path = f"/{item_text}"
            else:
                new_path = f"{current_path}/{item_text}"

        # 检查是否为目录
        try:
            attr = self.sftp_client.stat(new_path)
            if S_ISDIR(attr.st_mode):
                self.remote_path_var.set(new_path)
                self.refresh_remote_files()
        except Exception as e:
            messagebox.showerror("错误", f"无法导航到目录: {str(e)}")

    def upload_files(self):
        """上传选中的本地文件到远程服务器"""
        if not self.is_connected or not self.sftp_client:
            messagebox.showwarning("未连接", "请先连接到服务器")
            return

        selected_items = self.local_files_tree.selection()
        if not selected_items:
            messagebox.showinfo("提示", "请先选择要上传的文件")
            return

        local_path = self.local_path_var.get()
        remote_path = self.remote_path_var.get()

        # 在新线程中执行上传
        threading.Thread(target=self._upload_files_thread, args=(selected_items, local_path, remote_path),
                         daemon=True).start()

    def _upload_files_thread(self, selected_items, local_path, remote_path):
        """上传文件的线程"""
        try:
            with SCPClient(self.ssh_client.get_transport()) as scp:
                for item in selected_items:
                    file_name = self.local_files_tree.item(item, "text")
                    local_file_path = os.path.join(local_path, file_name)

                    # 显示上传信息
                    self.after(0, lambda fn=file_name: self.append_output(f"正在上传 {fn}...\n"))

                    # 上传文件
                    if os.path.isdir(local_file_path):
                        # 上传目录
                        scp.put(local_file_path, remote_path=remote_path, recursive=True)
                    else:
                        # 上传文件
                        scp.put(local_file_path, remote_path=remote_path)

                    # 显示上传完成信息
                    self.after(0, lambda fn=file_name: self.append_output(f"上传 {fn} 完成\n"))

            # 刷新远程文件列表
            self.after(0, self.refresh_remote_files)

        except Exception as e:
            self.after(0, lambda: self.append_output(f"上传文件时出错: {str(e)}\n"))
            self.after(0, lambda: messagebox.showerror("上传错误", f"上传文件时出错: {str(e)}"))

    def download_files(self):
        """下载选中的远程文件到本地"""
        if not self.is_connected or not self.sftp_client:
            messagebox.showwarning("未连接", "请先连接到服务器")
            return

        selected_items = self.remote_files_tree.selection()
        if not selected_items:
            messagebox.showinfo("提示", "请先选择要下载的文件")
            return

        local_path = self.local_path_var.get()
        remote_path = self.remote_path_var.get()

        # 在新线程中执行下载
        threading.Thread(target=self._download_files_thread, args=(selected_items, remote_path, local_path),
                         daemon=True).start()

    def _download_files_thread(self, selected_items, remote_path, local_path):
        """下载文件的线程"""
        try:
            with SCPClient(self.ssh_client.get_transport()) as scp:
                for item in selected_items:
                    file_name = self.remote_files_tree.item(item, "text")

                    # 构建完整的远程路径
                    if remote_path == "/":
                        remote_file_path = f"/{file_name}"
                    else:
                        remote_file_path = f"{remote_path}/{file_name}"

                    # 显示下载信息
                    self.after(0, lambda fn=file_name: self.append_output(f"正在下载 {fn}...\n"))

                    # 下载文件
                    scp.get(remote_file_path, local_path=local_path, recursive=True)

                    # 显示下载完成信息
                    self.after(0, lambda fn=file_name: self.append_output(f"下载 {fn} 完成\n"))

            # 刷新本地文件列表
            self.after(0, self.refresh_local_files)

        except Exception as e:
            self.after(0, lambda: self.append_output(f"下载文件时出错: {str(e)}\n"))
            self.after(0, lambda: messagebox.showerror("下载错误", f"下载文件时出错: {str(e)}"))

    def format_size(self, size_bytes):
        """格式化文件大小"""
        if size_bytes < 1024:
            return f"{size_bytes} B"
        elif size_bytes < 1024 * 1024:
            return f"{size_bytes / 1024:.2f} KB"
        elif size_bytes < 1024 * 1024 * 1024:
            return f"{size_bytes / (1024 * 1024):.2f} MB"
        else:
            return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"

    def format_time(self, timestamp):
        """格式化时间戳"""
        return time.ctime(timestamp)

    def on_close(self):
        """关闭窗口时的处理"""
        if self.is_connected:
            self.disconnect()
        self.destroy()

    def create_widgets(self):
        pass
